#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# pylint: disable-msg = C0301
"""This program displays information about kanji"""

import gtk
import sqlite3
import os
import sys
import ConfigParser
import re
try:
    import yaml
    yaml_present = True
    import gtrans
except ImportError:
    yaml_present = False

__author__ = "Durand D'souza"
__copyright__ = "Copyright 2010, Kuerī"
__license__ = "GPL"
__version__ = "0.2"
__email__ = "durand1[@]gmail[.]com"


# Get the location of the kueri.py
LOCATION = os.path.abspath(os.path.dirname(sys.argv[0]))
# Get the kanji db
KANJIDB = LOCATION + "/kanji.db"
# Get db languages
LANGS = [('English', 'en'), ('French', 'fr'), ('Spanish', 'es'), ('Portuguese', 'pt')]
# Get google languages
if yaml_present:
    GLANGS = yaml.load(file(LOCATION + '/langs.yml', 'r').read())
    # Correct for yaml's boolean action
    GLANGS['norwegian'] = "no"
else:
    GLANGS = {}

def dict_to_tdict(dict):
    """Converts a dictionary into a tuple list (tdict)"""
    tdict = [(key, value) for key, value in dict.iteritems()]
    tdict.sort()
    return tdict

# Convert it to a tuple list
GLANGS = dict_to_tdict(GLANGS)

# Try to get settings from the settings file
kueri_settings = ConfigParser.ConfigParser()
kueri_settings.read(os.path.expanduser('~/.kueri/settings'))
# Get fonts
try:
    kanji_display_font = kueri_settings.get('Fonts', 'kanji_display')
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
    kanji_display_font = "KaiTi 200"
try:
    kanji_stroke_font = kueri_settings.get('Fonts', 'kanji_stroke')
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
    kanji_stroke_font = "KanjiStrokeOrders 200"
try:
    kana_small_font = kueri_settings.get('Fonts', 'kana_small')
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
    kana_small_font = "KaiTi 10"
# Get meaning language
try:
    meanings_language = kueri_settings.get('Language', 'meanings')
except(ConfigParser.NoSectionError, ConfigParser.NoOptionError):
    meanings_language = "en"
# Get google language
try:
    google_meanings_language = kueri_settings.get('Language', 'google_meanings')
    google_meanings_language_set = True
except(ConfigParser.NoSectionError, ConfigParser.NoOptionError):
    google_meanings_language = meanings_language
    google_meanings_language_set = False
# If yaml isn't present, google language is false
if not yaml_present:
    google_meanings_language_set = False

CONNECTION = sqlite3.connect(KANJIDB)
CURSOR = CONNECTION.cursor()

def get_tdict_value(dict, key):
    """This function returns the value of the first key that is matched in a tuple list"""
    for item in dict:
        if item[0] == key:
            return item[1]

def get_tdict_key(dict, value):
    """This function returns the key of the first value that is matched in a tuple list"""
    for item in dict:
        if item[1] == value:
            return item[0]

def valid_kanji(kanji):
    """Check if kanji supplied is valid"""
    # If the kanji exists in the main table, then it is valid
    return CURSOR.execute("SELECT count() from main where literal = ? LIMIT 1", kanji).fetchall() == [(1,)]

def get_kanji(kanji):
    """Get details about a kanji supplied"""
    if valid_kanji(kanji):
        # Get the readings and meanings from the database
        on_readings = [ item[0] for item in CURSOR.execute("SELECT reading from readings where literal = ? and r_type = 'ja_on'", kanji).fetchall() ]
        kun_readings = [ item[0] for item in CURSOR.execute("SELECT reading from readings where literal = ? and r_type = 'ja_kun'", kanji).fetchall() ]
        # If google translate is enabled, use english as language
        if google_meanings_language_set:
            language = "en"
        else:
            language = meanings_language
        meanings = [ item[0] for item in CURSOR.execute("SELECT meaning from meanings where literal = ? and m_lang = ?", (kanji, language)).fetchall() ]
        
        # Get kanji grade, stroke count and frequency
        grade, stroke_count, freq = CURSOR.execute("SELECT grade, stroke_count, freq from main where literal = ?", kanji).fetchall()[0]
        
        # If google translate is enabled, translate meanings into new language
        if google_meanings_language_set:
            translated_meanings = [ translate(item) for item in meanings ]
        else:
            translated_meanings = meanings
        return {'meanings':translated_meanings, 'kun_readings':kun_readings, 'on_readings':on_readings, 'grade':grade, 'stroke_count':stroke_count, 'freq':freq}
    
    else:
        return None

def kanji_search(string):
    """This function returns a list of kanji to get info for"""
    list_of_kanji = []
    for kanji in list(string):
        if valid_kanji(kanji):
            list_of_kanji.append((kanji, kanji))
    return list_of_kanji

def meanings_search(string):
    """This function returns a list of kanji that match the meanings in the string"""
    list_of_kanji = []
    # For each word in the string, find relevant kanji
    for word in string.split(','): # Split by comma
        if google_meanings_language_set:
            translated_word = gtrans.translate(google_meanings_language, "en", word)
            language = "en"
        else:
            translated_word = word
            language = meanings_language
        list_of_kanji.extend([ (word, item[0]) for item in CURSOR.execute("SELECT literal FROM meanings WHERE meaning LIKE ? and m_lang = ?", (translated_word, language)).fetchall() ])
    return list_of_kanji

def readings_search(string):
    """This function returns a list of kanji that matches the readings in the string"""
    list_of_kanji = []
    # For each word in the string, find relevant kanji
    for word in string.split('、'): # Split by comma
        list_of_kanji.extend([ (word, item[0]) for item in CURSOR.execute("SELECT literal FROM readings WHERE reading LIKE ?", (word, )).fetchall() ])
    return list_of_kanji    

def stroke_count_search(string):
    """This function returns a list of kanji that have the stroke orders as in the string"""
    list_of_kanji = []
    # For each word in the string, find relevant kanji
    for query_string in string.split(','): # Split by comma
        # The string won't be escaped by sqlite so we need to make sure it's ok to run
        # First check that it isn't just a number
        try:
            query_string = int(query_string)
            # Add an equal to it so that it executes properly
            query_string = "=" + str(query_string)
        except ValueError: # If it throws an error, it isn't just a number
            pass
        # Check that the string is ok to execute, and if so, do it
        if re.match(" *[!<>]?[><=] *[0-9]+ *| *between +[0-9]+ +and +[0-9]+ *| *", query_string).group() == query_string:
            list_of_kanji.extend([ ("stroke_count " + query_string, item[0]) for item in CURSOR.execute("SELECT literal FROM main WHERE stroke_count %s"%(query_string)).fetchall() ])
        
    return list_of_kanji    

def get_from_anki(string):
    """This function gets the currently displayed card from anki and then displays the kanji in it.
    The anki kueri plugin is needed. We get the kanji from the anki file and pass it to kanji_search() to process"""
    # Get string from the anki file located in the kueri settings directory
    import codecs # to decode the unicode in the file
    try:
        anki_file = codecs.open(os.path.expanduser('~/.kueri/anki'), 'r', encoding='utf-8').read()
        list_of_kanji = []
        for kanji in list(anki_file):
            if valid_kanji(kanji):
                list_of_kanji.append((kanji, kanji))
        return list_of_kanji        
    except IOError:
        return []

def translate(string):
    """Translate strings using google"""
    if google_meanings_language_set and google_meanings_language != "en" and yaml_present:
        return gtrans.translate("en", google_meanings_language, string)
    else:
        return string

class Main(gtk.Window):
    """This is the application's main window"""
    def __init__(self):
        """Start the main window loop"""
        super(Main, self).__init__()
        # Set a title
        self.set_title("Kuerī - " + translate("Lookup Kanji"))
        # Set an icon
        gtk.window_set_default_icon_from_file(LOCATION + '/kueri.svg')
        # Center the window on the screen
        self.set_position(gtk.WIN_POS_CENTER)
        # Set inner border of window
        self.set_border_width(20)
        # Connect the destroy signal to the quit function
        self.connect("destroy", gtk.main_quit)
        
        # Add a vbox to contain everything
        vbox = gtk.VBox(spacing=10)
        self.add(vbox)
        # Add an hbox to contain search stuff
        query_hbox = gtk.HBox(spacing=2)
        vbox.pack_start(query_hbox, expand=False)
        
        # Add an entry box to the query hbox
        self.query_entry = gtk.Entry()
        self.query_entry.connect("activate", self.query_string)
        # Align text in the centre of the entry box
        self.query_entry.set_alignment(0.5)
        query_hbox.pack_start(self.query_entry, padding=10)
        
        # Add a dropdown to select type of data to search with
        self.query_type_combo = gtk.combo_box_new_text()
        # List of query types
        self.query_types = [('Kanji', kanji_search), ('Meaning', meanings_search), ('Reading', readings_search), ('Stroke Count', stroke_count_search), ('From Anki', get_from_anki)]
        for query_type in self.query_types:
            self.query_type_combo.append_text(query_type[0])
        self.query_type_combo.set_active(0)
        # Add dropdown to the table occupying a quarter of the first row
        query_hbox.pack_start(self.query_type_combo, expand=False)
        
        # Add a preferences button
        self.preferences_button = gtk.Button(stock=gtk.STOCK_PREFERENCES)
        self.preferences_button.connect("clicked", self.open_preferences)
        query_hbox.pack_start(self.preferences_button, expand=False)
                        
        # Add a submit button
        self.query_submit = gtk.Button("_Go")
        self.query_submit.connect("clicked", self.query_string)
        query_hbox.pack_start(self.query_submit, expand=False)
        
        # Create a notebook to display multiple kanji information
        self.notebook = gtk.Notebook()
        self.notebook.set_tab_pos(gtk.POS_TOP)
        self.notebook.set_scrollable(True)
        # Hide the border and tabs
        self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        self.notebook.popup_enable()
        # Attach the notebook to the vbox
        vbox.pack_start(self.notebook)
        
        # Add the default tab, which contains help message
        self.add_info_tab()

        self.resize(450, 320)
        self.show_all()
        
    def open_preferences(self, button):
        """Open the preferences window"""
        Preferences()
    
    def add_tab(self, info="", kanji="", title=None, border=True):
        """Add a new tab to the notebook"""
        if title == None:
            title = kanji
        # Make the tabs and borders show
        self.notebook.set_show_tabs(border)
        self.notebook.set_show_border(border)
        # Add a table to organise info
        tab_table = gtk.Table(rows=1)
        # Add a label to display the info for the kanji
        info_box = gtk.Label()
        info_box.set_markup(info)
        info_box.set_selectable(True)
        # And one to display the kanji in big
        kanji_box = gtk.Label()
        kanji_box.set_markup("""<span font_desc="%s">%s</span><span font_desc="%s">%s</span>"""%(kanji_display_font, kanji, kanji_stroke_font, kanji))
        
        kanji_box.set_selectable(True)
        
        # Attach the labels to the table
        tab_table.attach(kanji_box, 0, 1, 0, 1)
        tab_table.attach(info_box, 0, 1, 1, 2)
        # Make a tab label
        if title == kanji:
            label_text = kanji
        else:
            label_text = "%s (%s)"%(title, kanji)
        tab_label = gtk.Label(label_text)
        eventbox = gtk.EventBox()
        eventbox.add(tab_label)
        eventbox.connect("button-press-event", self.close_tab, tab_table)
        tab_label.show()
        # Add the table to a new tab
        self.notebook.append_page(tab_table, tab_label=eventbox)
        self.notebook.set_tab_reorderable(tab_table, True)
        self.notebook.set_menu_label_text(tab_table, label_text)
        self.show_all()

    def add_info_tab(self):
        """Add an info tab which contains a nice message"""
        self.add_tab(info="<span size='14000'>Type something above to look it up</span>", title="Help", border=False)
        
    def close_tab(self, object, event, widget):
        """Close the specified tab"""
        if event.type == gtk.gdk.BUTTON_PRESS:
            if event.button == 2:
                page = self.notebook.page_num(widget)
                self.notebook.remove_page(page)
                if self.notebook.get_n_pages() == 0:
                    self.add_info_tab()

    def remove_tab(self, button, child):
        """Remove a tab specified by its child"""
        self.notebook.remove_page(self.notebook.page_num(child))
        
    def add_kanji_tab(self, kanji, title):
        """Add a tab containing details about the kanji"""
        kanji_info = get_kanji(kanji)
        # Make a list of meanings in string form
        meanings = ""
        for meaning in kanji_info['meanings']:
            meanings = meanings + ", " + meaning
        # Make a list of kun readings in string form
        kun_readings = ""
        kun_readings_list = kanji_info['kun_readings']
        # Reverse it so that it is printed in the right order
        kun_readings_list.reverse()
        for reading in kun_readings_list:
            kun_readings = reading + "、" + kun_readings
        # Make a list of on readings in string form
        on_readings = ""
        on_readings_list = kanji_info['on_readings']
        # Reverse it so that it is printed in the right order
        on_readings_list.reverse()
        for reading in on_readings_list:
            on_readings = reading + "、" + on_readings
        # Misc Text
        if kanji_info['stroke_count'] == None:
            stroke_count = ""
        else:
            stroke_count = "<b>Stroke Count:</b> " + str(kanji_info['stroke_count'])
        if kanji_info['grade'] == None:
            grade = ""
        else:
            grade = "<b>Grade:</b> " + str(kanji_info['grade'])
        if kanji_info['freq'] == None:
            freq = ""
        else:
            freq = "<b>Rank:</b> " + str(kanji_info['freq'])
            
        misc_text = "%s %s %s"%(stroke_count, grade, freq)
        # Output text
        output_text = """<span size="large"><b>Meanings:</b> %s
<b>Kun Reading:</b> <span font_desc="%s">%s</span>
<b>On Reading:</b> <span font_desc="%s">%s</span></span>

%s"""%(meanings[2:], kana_small_font, kun_readings[:-1], kana_small_font, on_readings[:-1], misc_text)
        self.add_tab(info=output_text, kanji=kanji, title=title)
        
        # If there's more than one page, the title page needs to go
        if self.notebook.get_tab_label(self.notebook.get_nth_page(0)).get_children()[0].get_label() == "Help ()" and self.notebook.get_n_pages() > 1:
            self.notebook.remove_page(0)
    
    def query_string(self, widget):
        """Search for results from the string"""
        # Get kanji, split by character
        string = u"" + self.query_entry.get_text()
        # Get the type of query from the combobox
        query_type = self.query_type_combo.get_active_text()
        # Find the right function and execute it
        kanji_list = get_tdict_value(self.query_types, query_type)(string)
        # If the list of kanji exceeds about 20 kanji, ask the user for permission
        if len(kanji_list) > 20:
            message_dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format="%s kanji were found. Continue?"%(len(kanji_list)))
            response = message_dialog.run() == gtk.RESPONSE_YES
            message_dialog.destroy()
        else:
            response = True
        if len(kanji_list) == 0 and self.notebook.get_n_pages() == 0:
            self.add_info_tab()
        elif response:            
            # Go through each character and add it as a new tab
            for kanji_item in kanji_list:
                self.add_kanji_tab(kanji_item[1], kanji_item[0])
    
class Preferences(gtk.Window):
    """This is the preferences window"""
    def __init__(self):
        """This is the initial function"""
        super(Preferences, self).__init__()
        self.set_title("Kuerī　- Preferences")
        # Set inner border of window
        self.set_border_width(20)
        # Add a vbox to hold stuff
        preferences_vbox = gtk.VBox()
        self.add(preferences_vbox)
        preferences_table = gtk.Table(6, 2)
        preferences_table.set_row_spacings(2)
        preferences_vbox.pack_start(preferences_table, fill=False)
        
        # Add a label for the display font
        display_font_label = gtk.Label("Display font: ")
        display_font_label.set_alignment(0, 0.5)
        preferences_table.attach(display_font_label, 0, 1, 0, 1)
        # Add a button to choose the display font
        self.display_font_button = gtk.FontButton(fontname=kanji_display_font)
        preferences_table.attach(self.display_font_button, 1, 2, 0, 1, yoptions=gtk.EXPAND)
        
        # Add a label for the stroke order font
        stroke_font_label = gtk.Label("Stroke Order font: ")
        stroke_font_label.set_alignment(0, 0.5)
        preferences_table.attach(stroke_font_label, 0, 1, 1, 2)
        # Add a button to choose the stroke order font
        self.stroke_font_button = gtk.FontButton(fontname=kanji_stroke_font)
        preferences_table.attach(self.stroke_font_button, 1, 2, 1, 2, yoptions=gtk.EXPAND)
        
        # Add a label for the stroke order font
        small_font_label = gtk.Label("Small kana font: ")
        small_font_label.set_alignment(0, 0.5)
        preferences_table.attach(small_font_label, 0, 1, 2, 3)
        # Add a button to choose the stroke order font
        self.small_font_button = gtk.FontButton(fontname=kana_small_font)
        preferences_table.attach(self.small_font_button, 1, 2, 2, 3, yoptions=gtk.EXPAND)
        
        # Add a label for the meanings language
        self.meanings_language_label = gtk.Label("Meanings language: ")
        self.meanings_language_label.set_alignment(0, 0.5)
        preferences_table.attach(self.meanings_language_label, 0, 1, 3, 4)
        # Add a combo to set language
        self.meanings_language_combo = gtk.combo_box_new_text()
        # The kanjidb only contains info about these languages
        for language in LANGS:
            self.meanings_language_combo.append_text(language[0])
        # Set the saved language as the default
        self.meanings_language_combo.set_active(LANGS.index((get_tdict_key(LANGS, meanings_language), meanings_language)))
        preferences_table.attach(self.meanings_language_combo, 1, 2, 3, 4)
        
        # Add google language support
        # Add a label for google languages
        google_language_label = gtk.Label("Google translate: ")
        google_language_label.set_alignment(0, 0.5)
        preferences_table.attach(google_language_label, 0, 1, 4, 5)
        # Add a checkbox to use google instead of db
        self.google_language_checkbox = gtk.CheckButton("Translate meanings with Google")
        # Set it according to the google language variable
        self.google_language_checkbox.set_active(google_meanings_language_set)
        self.google_language_checkbox.connect('toggled', self.google_language_checkbox_toggle)
        preferences_table.attach(self.google_language_checkbox, 1, 2, 4, 5)
        # Add a combo to set language
        self.google_language_combo = gtk.combo_box_entry_new_text()
        # Add all the google languages
        for language in GLANGS:
            # Make the language name look nicer by adding spacing and capitals
            self.google_language_combo.append_text(language[0].replace('_', ' ').title())
        # Set the saved language as the default if it exists
        if yaml_present:
            try: # Might fail if language is pt rather than pt-PT
                self.google_language_combo.set_active(GLANGS.index((get_tdict_key(GLANGS, google_meanings_language), google_meanings_language)))
            except ValueError:
                self.google_language_combo.set_active(0)

        # If yaml doesn't exist, we disable the options
        self.meanings_language_combo.set_sensitive(google_meanings_language_set != True)
        google_language_label.set_sensitive(yaml_present)
        self.google_language_checkbox.set_sensitive(yaml_present)
        self.google_language_combo.set_sensitive(yaml_present and google_meanings_language_set)
        
        preferences_table.attach(self.google_language_combo, 1, 2, 5, 6)
            
        # Add OK, Apply and Cancel buttons
        buttons_align = gtk.Alignment(1, 1, 0.9, 0)
        preferences_apply_cancel_ok_hbox = gtk.HBox()
        buttons_align.add(preferences_apply_cancel_ok_hbox)
        preferences_vbox.pack_end(buttons_align)
        apply_button = gtk.Button(stock=gtk.STOCK_APPLY)
        apply_button.connect("clicked", self.apply_prefs)
        cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
        cancel_button.connect("clicked", self.quit_window)
        ok_button = gtk.Button(stock=gtk.STOCK_OK)
        ok_button.connect("clicked", self.save_prefs)
        
        preferences_apply_cancel_ok_hbox.pack_start(apply_button)
        preferences_apply_cancel_ok_hbox.pack_start(cancel_button)
        preferences_apply_cancel_ok_hbox.pack_start(ok_button)
        self.show_all()

    def google_language_checkbox_toggle(self, widget):
        """Enable or disable language options according to checkbox value"""
        global google_meanings_language_set
        google_meanings_language_set = widget.get_active()
        self.meanings_language_combo.set_sensitive(google_meanings_language_set != True)
        self.google_language_combo.set_sensitive(google_meanings_language_set)
                    
    def save_prefs(self, widget, quit=True):
        """Save the preferences in a configparser file and update them"""
        # Set them
        global kanji_display_font, kanji_stroke_font, kana_small_font, meanings_language, google_meanings_language, google_meanings_language_set
        kanji_display_font = self.display_font_button.get_font_name()
        kanji_stroke_font = self.stroke_font_button.get_font_name()
        kana_small_font = self.small_font_button.get_font_name()
        meanings_language = get_tdict_value(LANGS, self.meanings_language_combo.get_active_text())
        if self.google_language_checkbox.get_active():
            google_meanings_language = get_tdict_value(GLANGS, self.google_language_combo.get_active_text().lower().replace(' ', '_')) # Convert the text back to lower case et al
            google_meanings_language_set = True
        else:
            google_meanings_language = meanings_language
            google_meanings_language_set = False
        # Save them with ConfigParser
        kueri_settings = ConfigParser.ConfigParser()
        kueri_settings.add_section('Fonts')
        kueri_settings.set('Fonts', 'kanji_display', kanji_display_font)
        kueri_settings.set('Fonts', 'kanji_stroke', kanji_stroke_font)
        kueri_settings.set('Fonts', 'kana_small', kana_small_font)
        kueri_settings.add_section('Language')
        kueri_settings.set('Language', 'meanings', meanings_language)
        if google_meanings_language_set:
            kueri_settings.set('Language', 'google_meanings', google_meanings_language)
            
        
        # Create a file object for the settings file
        try:
            # Make a kueri folder first, just in case
            os.mkdir(os.path.expanduser('~/.kueri'))
        except OSError: # If it already exists, just ignore the error
            pass
        settings_file = open(os.path.expanduser('~/.kueri/settings'), 'w')
        # Write the settings to the file
        kueri_settings.write(settings_file)
        if quit:
            self.hide()
    
    def apply_prefs(self, widget):
        """Apply the settings without closing preferences"""
        self.save_prefs(widget, quit=False)
        
    def quit_window(self, widget):
        """Close the preferences window"""
        self.hide()

Main()
gtk.main()
